#ifndef DISK_H
#define DISK_H

#include <iostream>
#include <string>
#include <fstream>
#include <QPair>
#include <map>

using std::string;
using std::fstream;
using std::map;

class Disk
{
public:  
    Disk();

    Disk(string file_name, int capacity);

    /************** FAT操作 **************/
    void InitFatCache();

    /**
     * @brief 向 FAT 中写入一个条目
     * @param item first 为 key, second 为 value
     */
    void AddItemToFat(QPair<int, int> item);

    /**
     * @brief 从 FAT 中移除指定盘块号的条目
     * @param block_id
     */
    void RemoveItemFromFat(int block_id);

    /**
     * @brief 从 FAT 中查找一个条目并更新它
     * @param item
     */
    void UpdateFat(int block_id, int value);

    /**
     * @brief 将缓冲区的 FAT 写入文件
     */
    void SyncFat();

    /**
     * @brief 将所有缓冲写入文件
     */
    void Sync();

    int GetFatBytes();

    /**
     * @brief 在 FAT 中查找值
     * @param block_id 盘块号
     * @return
     */
    int GetFatValue(int block_id);
    
    int GetFatSize() {
        return fat_size_;
    }

    /************** 盘块操作 **************/
    
    /**
     * @brief 向盘块中读取编码
     * @param block_id
     * @return 盘块中的数字数组
     */
    std::vector<int> ReadCodeFromBlock(int block_id);
    
    /**
     * @brief 向盘块中写编码
     * @param block_id
     * @param codes
     * @return 成功与否
     */
    bool WriteCodeToBlock(int block_id, std::vector<int> codes);

    /**
     * @brief 获取下一个盘块的盘块号
     * @param cur_block_id
     */
    void GetNextBlock(int cur_block_id);

    /**
     * @brief 向指定块内写入
     * @param block_id
     */
    void WriteToBlock(int block_id, string content);

    /**
     * @brief 在指定块后添加数据
     * @param block_id
     */
    void AppendToBlock(int block_id, string content);

    /**
     * @brief 向磁盘中写入数据（寻找空块写入）
     * @param content
     * @return 成功写入返回对应的开始盘块号 反之返回 -1
     */
    int Write(string content);

    /**
     * @brief 从指定盘块号开始读取至文件结束
     * @param strat_block_id 开始的盘块号 ID
     * @return 读取到的内容
     */
    string Read(int strat_block_id);

    /**
     * @brief 从指定盘块号中读取内容
     * @param block_id 盘块号
     */
    void ReadFromBlock(int block_id, string &buffer);

    /**
     * @brief 从指定盘块号中读取内容
     * @param block_id 盘块号
     * @return 读取到的内容
     */
    string ReadFromBlock(int block_id);
    
    /**
     * @brief 更新起始盘块为 start_block_id 的内容，分配更多的块或清空无用的块
     * @param block_id
     * @param content
     * @return 
     */
    bool Update(int start_block_id, string content);
    
    /**
     * @brief 指定指定盘块清空
     * @param block_id
     */
    void ClearBlock(int block_id);
    
    /**
     * @brief 删除起始盘块为 block_id 的文件
     * @param block_id
     */
    void Clear(int block_id);

    /************** 顺序操作 **************/

    /**
     * @brief 往后获取一个块
     */
    void GetFrontBlock();

    /**
     * @brief 往回获取一个块
     */
    void GetBackBlock();

    /**
     * @brief 向当前块写入
     */
    void WriteCurrentBlock();
    
    /**
     * @brief 获取指定块在文件中的偏移
     * @param block_id
     * @return 
     */
    int GetBlockOffset(int block_id);
    
    bool DeleteBlock(int block_id);

    /************** 磁盘信息 **************/

    /**
     * @brief 获取磁盘已经占用的比例
     * @return 磁盘已经占用的比例
     */
    double GetDiskUsedRatio();

    /**
     * @brief 获取文件的结束标志
     * @return
     */
    int GetFileEndFlag();

    /**
     * @brief 获取盘块号数量的十进制长度
     * @return 盘块号数量的十进制长度
     */
    int GetCapacityLength();


    /************ 文件流操作 ************/
    void CloseStream() {
        file_stream_.close();
    }
    
    fstream* GetFstream() {
        return &file_stream_;
    }




private:

    /**
     * @brief 磁盘对应的文件名称
     */
    string file_name_;

    /**
     * @brief 文件流
     */
    fstream file_stream_;

    /**
     * @brief 操作系统占用的块数
     */
    int system_block_quantity_;

    /**
     * @brief FAT 占用的字节数
     */
    int fat_size_;

    /**
     * @brief FAT 的缓存
     */
    map<int, int> fat_cache_;

    /**
     * @brief 磁盘容量（字节数）
     */
    int capacity_;

    /**
     * @brief 盘块数量
     */
    int block_quantity_;


    /**
     * @brief 地址长度
     */
    int address_length_;

    /**
     * @brief 可用盘块的开始盘块号
     */
    int available_block_start_;
};

#endif // DISK_H
